package com.afollestad.materialdialogs.internal;

import com.afollestad.materialdialogs.GravityEnum;
import com.afollestad.materialdialogs.ResourceTable;
import com.afollestad.materialdialogs.StackingBehavior;
import com.afollestad.materialdialogs.utils.ResUtil;
import com.afollestad.materialdialogs.utils.UiUtil;
import ohos.agp.components.*;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.global.configuration.Configuration;
import org.jetbrains.annotations.Nullable;

/**
 * @author Kevin Barry (teslacoil) 4/02/2015 This is the top level view for all MaterialDialogs It
 * handles the layout of: titleFrame (md_stub_titleframe) content (text, custom view, listview,
 * etc) buttonDefault... (either stacked or horizontal)
 */
public class MDRootLayout extends DirectionalLayout implements Component.DrawTask {
    private static final int INDEX_NEUTRAL = 0, INDEX_NEGATIVE = 1, INDEX_POSITIVE = 2;
    private final MDButton[] buttons = new MDButton[3];
    private Component titleBar, content;
    private boolean drawTopDivider = false, drawBottomDivider = false, reducePaddingNoTitleNoButtons, noTitleNoPadding;
    private StackingBehavior stackBehavior = StackingBehavior.ADAPTIVE;
    private int maxHeight, noTitlePaddingFull, buttonPaddingFull, buttonBarHeight, dividerWidth;
    private GravityEnum buttonGravity = GravityEnum.START;
    /* Margin from dialog frame to first button */
    private Paint dividerPaint;
    private ComponentTreeObserver.ScrollChangedListener topOnScrollChangedListener, bottomOnScrollChangedListener;
    private Context mContext;
    private boolean firstRun = false;

    public MDRootLayout(Context context) {
        super(context);
        init(context);
    }

    public MDRootLayout(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MDRootLayout(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private static boolean isVisible(Component v) {
        boolean visible = v != null && v.getVisibility() != Component.HIDE;
        if (visible && v instanceof MDButton) {
            visible = ((MDButton) v).getText().trim().length() > 0;
        }
        return visible;
    }

    private static boolean canScrollViewScroll(ScrollView sv) {

        if (sv.getChildCount() == 0) {
            return false;
        }
        final int childHeight = sv.getComponentAt(0).getHeight();
        return sv.getHeight() - sv.getPaddingTop() - sv.getPaddingBottom() < childHeight;
    }

    private static boolean canAdapterViewScroll(ListContainer lv) {
        /* Force it to layout it's children */
        if (lv.getLastVisibleItemPosition() == -1) {
            return false;
        }

        /* We can scroll if the first or last item is not visible */
        boolean firstItemVisible = lv.getFirstVisibleItemPosition() == 0;
        boolean lastItemVisible = lv.getLastVisibleItemPosition() == lv.getChildCount() - 1;

        if (firstItemVisible && lastItemVisible && lv.getChildCount() > 0) {
            /* Or the first item's top is above or own top */
            if (lv.getComponentAt(0).getTop() < lv.getPaddingTop()) {
                return true;
            }
            /* or the last item's bottom is beyond our own bottom */
            return lv.getComponentAt(lv.getChildCount() - 1).getBottom() > lv.getHeight() - lv.getPaddingBottom();
        }
        return true;
    }

    /**
     * Find the view touching the bottom of this ViewGroup. Non visible children are ignored, however
     * getChildDrawingOrder is not taking into account for simplicity and because it behaves
     * inconsistently across platform versions.
     *
     * @param viewGroup
     * @return View touching the bottom of this ViewGroup or null
     */
    @Nullable
    private static Component getBottomView(ComponentContainer viewGroup) {
        if (viewGroup == null || viewGroup.getChildCount() == 0) {
            return null;
        }
        Component bottomView = null;
        for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
            Component child = viewGroup.getComponentAt(i);
            if (child.getVisibility() == Component.VISIBLE && child.getBottom() == viewGroup.getHeight()) {
                bottomView = child;
                break;
            }
        }
        return bottomView;
    }

    @Nullable
    private static Component getTopView(ComponentContainer viewGroup) {
        if (viewGroup == null || viewGroup.getChildCount() == 0) {
            return null;
        }
        Component topView = null;
        for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
            Component child = viewGroup.getComponentAt(i);
            if (child.getVisibility() == Component.VISIBLE && child.getTop() == 0) {
                topView = child;
                break;
            }
        }
        return topView;
    }

    private void init(Context context) {
        mContext = context;
        firstRun = true;
        reducePaddingNoTitleNoButtons = ResUtil.getBoolean(context, ResourceTable.Boolean_md_reduce_padding_no_title_no_buttons);
        noTitlePaddingFull = ResUtil.getIntDimen(context, ResourceTable.Float_md_notitle_vertical_padding);
        buttonPaddingFull = ResUtil.getIntDimen(context, ResourceTable.Float_md_button_frame_vertical_padding);
        buttonBarHeight = ResUtil.getIntDimen(context, ResourceTable.Float_md_button_height);

        dividerPaint = new Paint();
        dividerWidth = ResUtil.getIntDimen(context, ResourceTable.Float_md_divider_height);
        dividerPaint.setColor(new Color(ResUtil.getColor(context, ResourceTable.Color_md_divider_color)));
        addDrawTask(this);
    }

    public void setMaxHeight(int maxHeight) {
        this.maxHeight = maxHeight;
    }

    public void noTitleNoPadding() {
        noTitleNoPadding = true;
    }

    private void onFinishInflate() {
        for (int i = 0; i < getChildCount(); i++) {
            Component v = getComponentAt(i);
            if (v.getId() == ResourceTable.Id_md_titleFrame) {
                titleBar = v;
            } else if (v.getId() == ResourceTable.Id_md_buttonDefaultNeutral) {
                buttons[INDEX_NEUTRAL] = (MDButton) v;
            } else if (v.getId() == ResourceTable.Id_md_buttonDefaultNegative) {
                buttons[INDEX_NEGATIVE] = (MDButton) v;
            } else if (v.getId() == ResourceTable.Id_md_buttonDefaultPositive) {
                buttons[INDEX_POSITIVE] = (MDButton) v;
            } else {
                content = v;
            }
        }
    }

    private void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        if (height > maxHeight) {
            height = maxHeight;
        }

        boolean hasButtons = false;

        final boolean stacked;
        if (stackBehavior == StackingBehavior.ALWAYS) {
            stacked = true;
        } else if (stackBehavior == StackingBehavior.NEVER) {
            stacked = false;
        } else {
            int buttonsWidth = 0;
            for (MDButton button : buttons) {
                if (button != null && isVisible(button)) {
                    button.setStacked(false, false);
                    new UiUtil(getContext()).measureChild(button, widthMeasureSpec, heightMeasureSpec);
                    buttonsWidth += button.getWidth();
                    hasButtons = true;
                }
            }

            int buttonBarPadding = ResUtil.getIntDimen(getContext(), ResourceTable.Float_md_neutral_button_margin);
            final int buttonFrameWidth = width - 2 * buttonBarPadding;
            stacked = buttonsWidth > buttonFrameWidth;
        }

        int stackedHeight = 0;
        if (stacked) {
            for (MDButton button : buttons) {
                if (button != null && isVisible(button)) {
                    button.setStacked(true, false);
                    new UiUtil(getContext()).measureChild(button, widthMeasureSpec, heightMeasureSpec);
                    stackedHeight += button.getHeight();
                    hasButtons = true;
                }
            }
        }

        int availableHeight = height;
        int fullPadding = 0;
        int minPadding = 0;
        if (hasButtons) {
            if (stacked) {
                availableHeight -= stackedHeight;
                fullPadding += 2 * buttonPaddingFull;
                minPadding += 2 * buttonPaddingFull;
            } else {
                availableHeight -= buttonBarHeight;
                fullPadding += 2 * buttonPaddingFull;
                /* No minPadding */
            }
        } else {
            /* Content has 8dp, we add 16dp and get 24dp, the frame margin */
            fullPadding += 2 * buttonPaddingFull;
        }

        if (isVisible(titleBar)) {
            new UiUtil(getContext()).measure(width, height);
            availableHeight -= titleBar.getHeight();
        } else if (!noTitleNoPadding) {
            fullPadding += noTitlePaddingFull;
        }

        if (isVisible(content)) {
            new UiUtil(getContext()).measure(width, height);
            if (content.getHeight() <= availableHeight - fullPadding) {
                if (!reducePaddingNoTitleNoButtons || isVisible(titleBar) || hasButtons) {
                    availableHeight -= content.getHeight() + fullPadding;
                } else {
                    availableHeight -= content.getHeight() + minPadding;
                }
            } else {
                availableHeight = 0;
            }
        }
        int upDatedWidth = widthMeasureSpec - 70;
        int upDatedHeight = heightMeasureSpec + 70;
        int lcHeight = upDatedHeight - availableHeight;
        mInterface.onMeasureDimensions(upDatedWidth, upDatedHeight, lcHeight);
        setUpDividersVisibility(content, true, true);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (firstRun) {
            onFinishInflate();
            firstRun = false;
            onMeasure(getWidth(), getHeight());
        }
        if (content != null) {
            if (drawTopDivider) {
                int y = content.getTop();
                canvas.drawRect(new RectFloat(0, y - dividerWidth, getWidth(), y), dividerPaint);
            }

            if (drawBottomDivider) {
                int y = content.getBottom();
                canvas.drawRect(new RectFloat(0, y, getWidth(), y + dividerWidth), dividerPaint);
            }
        }
    }

    public void setStackingBehavior(StackingBehavior behavior) {
        stackBehavior = behavior;
        invalidate();
    }

    public void setButtonGravity(GravityEnum gravity) {
        buttonGravity = gravity;
        invertGravityIfNecessary();
    }

    private void invertGravityIfNecessary() {
        if (mContext == null) {
            return;
        }
        Configuration config = mContext.getResourceManager().getConfiguration();
        if (config.direction == LayoutDirection.RTL.ordinal()) {
            switch (buttonGravity) {
                case START:
                    buttonGravity = GravityEnum.END;
                    break;
                case END:
                    buttonGravity = GravityEnum.START;
                    break;
            }
        }
    }

    public void setButtonStackedGravity(GravityEnum gravity) {
        for (MDButton mButton : buttons) {
            if (mButton != null) {
                mButton.setStackedGravity(gravity);
            }
        }
    }

    private void setUpDividersVisibility(final Component view, final boolean setForTop, final boolean setForBottom) {
        if (view == null) {
            return;
        }
        if (view instanceof ScrollView) {
            final ScrollView sv = (ScrollView) view;
            if (canScrollViewScroll(sv)) {
                addScrollListener(sv, setForTop, setForBottom);
            } else {
                if (setForTop) {
                    drawTopDivider = false;
                }
                if (setForBottom) {
                    drawBottomDivider = false;
                }
            }
        } else if (view instanceof ListContainer) {
            final ListContainer sv = (ListContainer) view;
            if (canAdapterViewScroll(sv)) {
                addScrollListener(sv, setForTop, setForBottom);
            } else {
                if (setForTop) {
                    drawTopDivider = false;
                }
                if (setForBottom) {
                    drawBottomDivider = false;
                }
            }
        } else if (view instanceof ComponentContainer) {
            Component topView = getTopView((ComponentContainer) view);
            setUpDividersVisibility(topView, setForTop, setForBottom);
            Component bottomView = getBottomView((ComponentContainer) view);
            if (bottomView != topView) {
                setUpDividersVisibility(bottomView, false, true);
            }
        }
    }

    private void addScrollListener(final ComponentContainer vg, final boolean setForTop, final boolean setForBottom) {
        if ((!setForBottom && topOnScrollChangedListener == null || (setForBottom
                && bottomOnScrollChangedListener == null))) {
            if (vg instanceof ListContainer) {
                ScrolledListener scrollListener = (component, i, i1, i2, i3) -> {
                    boolean hasButtons = false;
                    for (MDButton button : buttons) {
                        if (button != null && button.getVisibility() != Component.HIDE) {
                            hasButtons = true;
                            break;
                        }
                    }
                    invalidateDividersForScrollingView(vg, setForTop, setForBottom, hasButtons);
                    invalidate();
                };
                vg.setScrolledListener(scrollListener);
                scrollListener.onContentScrolled(vg, 0, 0, 0, 0);
            } else {
                ComponentTreeObserver.ScrollChangedListener onScrollChangedListener = () -> {
                    boolean hasButtons = false;
                    for (MDButton button : buttons) {
                        if (button != null && button.getVisibility() != Component.HIDE) {
                            hasButtons = true;
                            break;
                        }
                    }
                    invalidateDividersForScrollingView(vg, setForTop, setForBottom, hasButtons);
                    invalidate();
                };
                if (!setForBottom) {
                    topOnScrollChangedListener = onScrollChangedListener;
                    vg.getComponentTreeObserver().addScrolledListener(topOnScrollChangedListener);
                } else {
                    bottomOnScrollChangedListener = onScrollChangedListener;
                    vg.getComponentTreeObserver().addScrolledListener(bottomOnScrollChangedListener);
                }
                onScrollChangedListener.onScrolled();
            }
        }
    }

    private void invalidateDividersForScrollingView(ComponentContainer view, final boolean setForTop, boolean setForBottom, boolean hasButtons) {
        if (setForTop && view.getChildCount() > 0) {
            drawTopDivider = titleBar != null && titleBar.getVisibility() != Component.HIDE &&
                    //Not scrolled to the top.
                    view.getScrollValue(AXIS_X) + view.getPaddingTop() > view.getComponentAt(0).getTop();
        }
        if (setForBottom && view.getChildCount() > 0) {
            drawBottomDivider = hasButtons
                    && view.getScrollValue(AXIS_Y) + view.getHeight() - view.getPaddingBottom() < view.getComponentAt(
                    view.getChildCount() - 1).getBottom();
        }
    }

    private OnLayoutMeasureDimensions mInterface;

    public interface OnLayoutMeasureDimensions {
        void onMeasureDimensions(int width, int height, int lcHeight);
    }

    public void setLayoutMeasureDimensions(OnLayoutMeasureDimensions layoutMeasureDimensions) {
        mInterface = layoutMeasureDimensions;
    }
}
